package Juc.高级;

import java.util.HashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;
class Phone2 implements Runnable {
    //锁的是资源类方法
    public  synchronized void m1() {
        System.out.println(Thread.currentThread().getId()+Thread.currentThread().getName()+"****外");
        try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace(); }
        m2();//当有线程获得m1时   他会同时获得m2的锁  即m2也被锁住了  其他不可调用
    }
    public  synchronized void m2(){
        System.out.println(Thread.currentThread().getId()+Thread.currentThread().getName()+"****内");
    }
    Lock lock=new ReentrantLock();
    @Override
    public void run(){
        get();
    }
    public void get(){
        /**
         * 线程可以进入任何一个它已经拥有的锁
         * 所同步着的代码块
         */
        //lock.lock();
        lock.lock();  //可以加锁可多次  但是也要解锁几次
        try {
            System.out.println(Thread.currentThread().getId()+Thread.currentThread().getName()+"****lock外外外");
            set();
        }finally {
            //lock.unlock();
            lock.unlock();
        }
    }
    public void set(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getId()+Thread.currentThread().getName()+"****lock内内内");
        }finally {
            lock.unlock();
        }
    }

}

public class AQS {
    public static void main(String[] args) {

        LockSupportDemo();
    }

    private static void AQSTest() {

        /**
         * AQS
         * 是用来构建锁或者其它同步器组件的重量级基础框架及整个JUC体系的基石，
         * 通过内置的FIFO队列来完成资源获取线程的排队工作，并通过一个int类型变量
         * 表示持有锁的状态
         * ReentrantLock
         * CountDownLatch
         * ReentrantReadWriteLock
         * Semaphore
         * 都继承于AQS//AbstractQueuedSynchronizer
         *              抽象的  队列   同步器
         */
        ReentrantLock lock=new ReentrantLock(false);//默认非公平锁  true公平锁
        lock.lock();lock.unlock();
        new Thread(()->{
            lock.lock();
            /**
             * ①第一次   启动锁 执行 sync.lock();
             *   发现当前lock的AQS的state为0表示没有人占有🔒  进入运行状态state = 1
             * 执行  setExclusiveOwnerThread(current); 当前线程占有锁
             */
            try {
                System.out.println("aaa");
                try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) { e.printStackTrace(); }
            }finally {
                lock.unlock();
                /**
                 * ④ 调用 sync.release(1);
                 *          release：if (tryRelease(arg)) {
                 *          Node = head ;
                 *          if (h != null && h.waitStatus != 0)
                 *              unparkSuccessor(h) ;//h为哨兵节点
                 *              return true;
                 *          }
                 *   1.tryRelease(1)
                 *      获取AQS状态state（1为被占用中） 因为当前a占用着 所以为1
                 *          1-1=0
                 *          如果为0  则说明本线程占用完了  先把当前正在占用的线程设置为空：setExclusiveOwnerThread(node)
                 *          设置当前AQS的state状态为 0
                 *          返回true  进入if里
                 *   2.unparkSuccessor
                 *          查看哨兵节点的状态 waitStatus==-1时 改为0
                 *          if哨兵节点的下一个节点  为空或者哨兵节点的下一个节点的waitStatus>0  执行 （当前为默认值0）不满足
                 *
                 *          if哨兵节点的下一个节点 不为空   打开哨兵节点的下一个节点的执行LockSupport.unpake() 加令牌 ： 释放锁
                 *                  并将AQS的start改为0
                 *
                 */
            }

        },"a").start();
        new Thread(()->{
            /**
             * ②第二次启动锁  判断有没有线程正在占用🔒  如果state = 0   则同①
             *               state = 1   说明有人占用  启动acquire(1)方法
             *              if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg)){
             *                  selfInterrupt();
             *              1.tryAcquire(arg):方法（抢占资源）   arg=1//模板设计模式  直接给你抛个异场  必须重写方法
             *                  1.1：会在获得一次state的值  若为0  说明他刚好又释放了锁 b刚进去  a刚释放锁
             *                        会对执行state CAS  把他状态改为 1  成功更改  说明b占有成功了 返回true
             *                  若为 1：
             *                  1.2：擦看当前占有的线程和自己是否一样   一样的话（可重入锁）state +=1  返回true
             *                  否则返回false
             *              2.acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
             *                   2.1：addWaiter(Node.EXCLUSIVE)//入队（进入排队）
             *                           AQS 有node的  头节点head  和尾节点tail
             *                           每个node有：
             *                            volatile Node prev;前一个       volatile Node next;后一个
             *                            Thread waiter;线程           volatile int waitStatus（status）;状态
             *                         2.1.1 先查看为节点是否为空
             *                               不空的话 见③
             *                               空的话  进入enq方法（）//将此节点入队
             *                                      enq：自旋（会循环）  在判断尾指针是否为空
             *                                         空的话 会自己建一个空的node    CASHead（比较并设置头节点）
             *                                                  setExclusiveOwnerThread(node)//把当前线程加到队列里
             *                                              节点即node的waiter为null（此间点成为傀儡节点/哨兵节点）为了占位
             *                                              将头节点和尾节点都设置为为这个  傀儡节点
             *                                         非空的话  b线程的node的 prev设置为尾节点
             *                                                   CASTail(比较并设置尾节点）将尾节点 设置为当前这个node
             *                                                           成功后：将上一个节点的next设置为当前这个node
             *                   2.2acquireQueued
             *                           会设置两个boolean     failed(记录他是否继续排队)
             *                           interrupted  记录当前线程是否占用线程  起始为false
             *                   自旋  ： Ⅰ.判断头节点是否为哨兵节点（就是） 在执行
             *                               1.tryAcquire(arg):方法（抢占资源）   arg=1//模板设计模式  直接给你抛个异场  必须重写方法
             *                                 1.1：会在获得一次state的值  若为0    a释放了锁
             *                                       会对执行state CAS  把他状态改为 1  成功更改  说明b占有成功了
             *                                         执行setHead先把AQS头节点head改为队列的 b这个节点
             *                                            队列的b节点的Thread =null  队列的b节点的prev=null（前一个结点）
             *                                            队列的b节点变成了哨兵节点
             *                                         把之前的哨兵节点的next=null（之前的哨兵节点就没有引用了 要被GC了）
             *                                       返回true
             *                                 若为 1：
             *                                 1.2：擦看当前占有的线程和自己是否一样   一样的话（可重入锁）state +=1  返回true
             *                                 否则返回false
             *                          Ⅱ。1.shouldParkAfterFailedAcquire()方法
             *                                   第一次自旋将当前头节点的状态 waitStatus（默认为0） = -1 返回false
             *                                   第二次自旋    头节点的状态 waitStatus == -1 了  返回true
             *                              2.parkAndCheckInterrupt  执行LockSupport.park(this)
             *                                //挂起了这个线程 b线程   停在了这里
             *                                  当④释放锁后  将interrupted=true
             *
             *
             */
            lock.lock();//
            try {
                System.out.println("bbb");
            }finally {
                lock.unlock();
            }

        },"b").start();

        new Thread(()->{
            lock.lock();
            /**
             * ③ 如法炮制  ②  如果不行  开始②一样加入队列  一样自旋等待并且LockSupport.park(this)
             *          AQS 有node的  头节点head  和尾节点tail
             *          每个node有：
             *          volatile Node prev;前一个       volatile Node next;后一个
             *          Thread waiter;线程            volatile int waitStatus（status）;状态
             *          尾指针tail 的addWaiter(Node.EXCLUSIVE)
             *          先查看为节点是否为空
             *               空的话 ②讲过
             *              *不空的话：
             *                  c线程的node的prev=AQS的tail
             *                  CASTail(比较并设置尾节点）将尾节点 设置为当前这个node
             *                         成功后：将上一个节点的next设置为当前这个node
             *
             */
            try {
                System.out.println("ccc");
            }finally {
                lock.unlock();
            }

        },"c").start();
    }

    private static void LockSupportDemo() {
        /**
         * LockSupport//不需要锁块  synchronized
         * park()//阻塞队列        //wait()和 await()     升级版   //
         * unpark()//关闭阻塞队列   //notify()和 signal()    升级版 //
         * wait()和notify() 方法必须要在同步块或者方法里面且成对出现使用
         * wait()一定在notify()前面执行
         * 线程先要获得并持有锁，必须在锁块(synchronized或lock)
         * 中必须要先等待后唤醒，线程才能够被唤醒
         *
         * unpark()可以在park()前执行   等于提前唤醒//native方法
         * unpark()发放许可证 用一次+1 但是最大为一
         * park()消耗许可证   为0消耗不了被堵塞  直到再发许可证
         *
         *用于创建锁和其他同步类的基本线程阳塞原语。
         */

        Thread a = new Thread(()->{LockSupport.park();System.out.println("6661");},"a");//park 必须有线程给他发放许可证  才往后执行
//        Thread a1 = new Thread(()->{LockSupport.park();System.out.println("a1");},"a1");//park 必须有线程给他发放许可证  才往后执行

        Thread b = new Thread(()->{LockSupport.unpark(a);System.out.println("777");},"b");//unpark 给指定线程发放许可证
//        Thread b1 = new Thread(()->{LockSupport.unpark(a);LockSupport.unpark(a);System.out.println("777");},"b");//unpark 给指定线程发放许可证
//        Thread b2 = new Thread(()->{LockSupport.unpark(a);LockSupport.unpark(a);System.out.println("777");},"b");//unpark 给指定线程发放许可证

        a.start();//先输出777  在输出666
        try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}
//        b1.start();
        b.start();
//        b2.start();

//        a1.start();
    }

    private static void 可重入锁() {
//         *    synchronized和ReentrantLock  可重入锁又名递归锁
//         *     线程可以获得自己线程
        Lock lock=new ReentrantLock();//默认 非公平锁 synchronized也是非公平锁
        Lock lock0=new ReentrantLock(false);//非公平锁  上来先尝试占有锁 如果尝试失败   就开始采取公平锁类似的方式
        Lock lock1=new ReentrantLock(true);
        //公平锁  每个线程先查看等待队列，如果为空或者是等待队列的第一个，就占有所，否则就会加入等待队列中  进行FIFO规则

        /**
         *   线程在外层方法获取锁的时候
         *   在进入内层方法会自动获取锁
         *
         *   线程可以进入任何一个它已经拥有的锁
         *   所同步着的代码块
         */
        Phone2 phone2=new Phone2();
        new Thread(()->{try {phone2.m1();} catch (Exception e) {e.printStackTrace();}},"t1").start();
        new Thread(()->{try {phone2.m2();} catch (Exception e) {e.printStackTrace();}},"t2").start();
//        new Thread(phone2,"t3").start();
//        new Thread(phone2,"t4").start();
    }

}
